home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / The World of Computer Software.iso / snws190s.zip / SNEWS.C < prev    next >
C/C++ Source or Header  |  1992-04-06  |  32KB  |  1,127 lines

  1. /*
  2.     SNEWS 1.90α
  3.  
  4.     snews - a simple threaded news reader
  5.  
  6.  
  7.     Copyright (C) 1991  John McCombs, Christchurch, NEW ZEALAND
  8.                         john@ahuriri.gen.nz
  9.                         PO Box 2708, Christchurch, NEW ZEALAND
  10.  
  11.     This program is free software; you can redistribute it and/or modify
  12.     it under the terms of the GNU General Public License, version 1, as
  13.     published by the Free Software Foundation.
  14.  
  15.     This program is distributed in the hope that it will be useful,
  16.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.     GNU General Public License for more details.
  19.  
  20.     See the file COPYING, which contains a copy of the GNU General
  21.     Public License.
  22.  
  23.  */
  24.  
  25. #include "defs.h"
  26. #include "snews.h"
  27. #include <alloc.h>
  28. #include <ctype.h>
  29.  
  30. int xfile = FALSE;
  31.  
  32. INFO my_stuff;
  33.  
  34.  
  35. /*------------------------------- main --------------------------------*/
  36. void main(void)
  37. {
  38.     ACTIVE *gp, *head;
  39.     int    done;
  40.  
  41.     printf("loading config... ");
  42.     if (load_stuff()) {
  43.  
  44.         printf("loading active... ");
  45.         head = load_active_file();
  46.         printf("loading read list... ");
  47.         load_read_list();
  48.         printf("loading history... ");
  49.         load_history_list();
  50.  
  51.         done = FALSE;
  52.         gp = NULL;
  53.  
  54.         while (!done) {
  55.             if ((gp = select_group(head, gp)) != NULL) {
  56.                 done |= read_group(gp);
  57.             } else {
  58.                 done = TRUE;
  59.             }
  60.         }
  61.  
  62.         clrscr();
  63.  
  64.         free_hist_list();
  65.         save_read_list();
  66.         close_active_file();
  67.     } else {
  68.     fprintf(stderr, "Couldn't find neccessary item in the .rc files\n");
  69.     }
  70. }
  71.  
  72.  
  73.  
  74.  
  75. /*-------------------------- find which group to read -----------------------*/
  76. ACTIVE *select_group(ACTIVE *head, ACTIVE *current)
  77. {
  78.     /*
  79.      *  Present the list of groups, and allow him to move up and down with
  80.      *  the arrow and PgUp and PgDn keys.  'h' for help, -/+ for searches
  81.      */
  82.  
  83.     ACTIVE *top;        /* newsgroup at the top of the page */
  84.     ACTIVE *this;       /* current newsgroup                */
  85.     ACTIVE *tmp_ng;
  86.     int    exit_code;   /* why we are exiting the loop      */
  87.     char   sub_tmp[80];
  88.  
  89.     int    ch, i, j, articles, unread;
  90.  
  91.     this = (current == NULL) ? head : current;
  92.  
  93.     top = head;
  94.     exit_code = 0;
  95.  
  96.     show_groups(&top, this, TRUE);
  97.  
  98.     while (exit_code == 0) {
  99.  
  100.         ch = getch();
  101.         switch (ch) {
  102.  
  103.             case 0      :
  104.                 ch = getch();
  105.  
  106.                 switch (ch) {
  107.  
  108.             case Fn1    :
  109.             show_help(HELP_GROUP);
  110.             show_groups(&top, this, TRUE);
  111.             break;
  112.  
  113.             case Fn2    :
  114.             show_values();
  115.             show_groups(&top, this, TRUE);
  116.             break;
  117.  
  118.                     case UP_ARR :
  119.                         if (this->last != NULL) this = this->last;
  120.                         break;
  121.  
  122.                     case DN_ARR :
  123.                         if (this->next != NULL) this = this->next;
  124.                         break;
  125.  
  126.                     case PGUP   :
  127.                         for (i = 0; i < PAGE_LENGTH; i++) {
  128.                             if (this->last == NULL) break;
  129.                             this = this->last;
  130.                         }
  131.                         break;
  132.  
  133.                     case PGDN   :
  134.                         for (i = 0; i < PAGE_LENGTH; i++) {
  135.                             if (this->next == NULL) break;
  136.                             this = this->next;
  137.                         }
  138.                         break;
  139.  
  140.                     case HOME   :
  141.                         top = this = head;
  142.                         show_groups(&top, this, TRUE);
  143.                         break;
  144.                     case END    :
  145.                         this = head;
  146.                         while (this->next != NULL)
  147.                             this = this->next;
  148.                         break;
  149.                 }
  150.                 break;
  151.  
  152.             case 'p'    :
  153.                 strcpy(sub_tmp, "");
  154.                 post(NULL, this->group, sub_tmp);
  155.                 show_groups(&top, this, TRUE);
  156.                 break;
  157.  
  158.             case '?'    :
  159.             case 'h'    :
  160.                 show_help(HELP_GROUP);
  161.                 show_groups(&top, this, TRUE);
  162.                 break;
  163.  
  164.             case 'c'    :
  165.                 mark_group_as_read(this);
  166.                 show_groups(&top, this, TRUE);
  167.                 break;
  168.  
  169.             case TAB    :
  170.                 tmp_ng = this->next;
  171.                 unread = 0;
  172.                 while (tmp_ng != NULL) {
  173.                     articles = (int) (tmp_ng->hi_num - tmp_ng->lo_num);
  174.                     for (j = 0; j < articles; j++) {
  175.                         if ( *((tmp_ng->read_list)+j) == FALSE)
  176.                             unread++;
  177.                     }
  178.                     if (unread > 0) break;
  179.                     tmp_ng = tmp_ng->next;
  180.                 }
  181.                 if (unread > 0) {
  182.                     this = tmp_ng;
  183.                 } else {
  184.                     message("-- No more articles to read --");
  185.                 }
  186.                 break;
  187.  
  188.             case ENTER  :
  189.                 exit_code = EX_DONE;
  190.                 break;
  191.  
  192.             case ESCAPE :
  193.                 exit_code = EX_QUIT;
  194.                 break;
  195.         };
  196.         if (exit_code == 0)
  197.             show_groups(&top, this, FALSE);
  198.     }
  199.  
  200.     if (exit_code == EX_DONE)
  201.         return(this);
  202.     else
  203.         return(NULL);
  204.  
  205. }
  206.  
  207.  
  208.  
  209. /*---------------------------- help screen ----------------------------------*/
  210. void show_help(int h)
  211. {
  212.  
  213.     char *type[] = {"New Group",  "Thread",  "Article"};
  214.  
  215.     clrscr();
  216.     textbackground(headb);    textcolor(headf);
  217.     clreol();
  218.     cprintf("         %s Help  (%s)\r\n", type[h], VERSION);
  219.     clreol();
  220.     textbackground(helpb);    textcolor(helpf);
  221.  
  222.     switch (h) {
  223.         case HELP_GROUP   :
  224.  
  225.     cprintf("\r\n\r\n");
  226.     cprintf("   LIST NEWSGROUPS                     CHOOSING NEWSGROUPS\r\n\r\n");
  227.     cprintf("   PgUp  move display up one page      TAB    go to next unread newsgroup\r\n");
  228.     cprintf("   PgDn  move display down one page    ENTER  read selected newsgroup\r\n");
  229.     cprintf("   Home  move to top of list\r\n");
  230.     cprintf("   End   move to bottom of list\r\n");
  231.     cprintf("   \x18     move up one line\r\n");
  232.     cprintf("   \x19     move down one line\r\n\r\n");
  233.          /* 1                                      2                               */
  234.     cprintf("   POST ARTICLE                        SPECIAL ACTIONS\r\n\r\n");
  235.     cprintf("   p     post article in newsgroup     c      mark this newsgroup as read\r\n");
  236.     cprintf("                                       F2     show user values\r\n");
  237.  
  238.     break;
  239.  
  240.         case HELP_THREAD  :
  241.          /* 1                                   2                                     */
  242.     cprintf("\r\n\r\n");
  243.     cprintf("   LISTING THREADS                     READING THREADS\r\n\r\n");
  244.  
  245.     cprintf("   PgUp  move display up one page      TAB    read next unread article\r\n");
  246.     cprintf("   PgDn  move display down one page    ENTER  read the selected thread\r\n");
  247.     cprintf("   Home  move to top of list           \x1a      read the selected thread\r\n");
  248.     cprintf("   End   move to bottom of list\r\n");
  249.     cprintf("   \x18     move up one line\r\n");
  250.     cprintf("   \x19     move down one line\r\n\r\n");
  251.  
  252.     cprintf("   MISCELLANEOUS ACTIONS               EXTRACT THREADS\r\n\r\n");
  253.     cprintf("   p     post article in newsgroup     s      save thread to disk\r\n");
  254.     cprintf("   c     mark this newsgroup as read   w      extract article to \\EXTRACT.NWS\r\n");
  255.     cprintf("   F2    show user values\r\n");
  256.  
  257.     break;
  258.  
  259.         case HELP_ARTICLES:
  260.  
  261.     cprintf("\r\n\r\n");
  262.     cprintf("   READING ARTICLES                    CHOOSING ARTICLES\r\n\r\n");
  263.     cprintf("   PgUp  move display up one page      TAB    read next unread article\r\n");
  264.     cprintf("   PgDn  move display down one page    ENTER  read next article\r\n");
  265.     cprintf("   Home  move to top of article        \x1a      read next article in thread\r\n");
  266.     cprintf("   End   move to bottom of article     \x1b      read prior article in thread\r\n");
  267.     cprintf("   \x18     move up one line\r\n");
  268.     cprintf("   \x19     move down one line\r\n");
  269.  
  270.     cprintf("                                       SEND MAIL\r\n");
  271.     cprintf("   POST ARTICLE\r\n");
  272.     cprintf("                                       r    mail reply to author\r\n");
  273.     cprintf("   p     post new article              R    mail reply to someone\r\n");
  274.     cprintf("   f     post follow-up article        m    mail article to someone\r\n\r\n");
  275.          /* 1                                   2                               */
  276.  
  277.     cprintf("   SPECIAL ACTION                      EXTRACT ARTICLES\r\n\r\n");
  278.     cprintf("   x     decode ROT-13 article         s    save article to file\r\n");
  279.     cprintf("   c     mark all articles as read     w    extract article to \\EXTRACT.NWS\r\n");
  280.     cprintf("   F2    show user values\r\n");
  281.         break;
  282.     };
  283.  
  284.     message("-- Press any key to continue --");
  285.     getch();
  286.  
  287. }
  288.  
  289. /*-------------- show the values of user defined variables -------------*/
  290. void show_values(void)
  291. {
  292.     clrscr();
  293.     textbackground(headb);    textcolor(headf);
  294.     clreol();
  295.     cprintf("    Pre-set User values  (%s)\r\n", VERSION);
  296.     clreol();
  297.     textbackground(helpb);    textcolor(helpf);
  298.     cprintf("\r\n\r\n");
  299.  
  300.     cprintf("   User Name:   %s@%s  (%s)\r\n\r\n",my_stuff.user, my_stuff.my_domain,
  301.                            my_stuff.my_name);
  302.  
  303.     cprintf("   Local node name:    %s\r\n",my_stuff.my_site);
  304.     cprintf("   Mail Server:        %s\r\n",my_stuff.mail_server);
  305.     cprintf("   Organization:       %s\r\n\r\n",my_stuff.my_organisation);
  306.  
  307.     cprintf("   Signature File:     %s\r\n",my_stuff.signature);
  308.     cprintf("   Reply-To:           %s\r\n",my_stuff.replyuser);
  309.     cprintf("   Aliases file:       %s\r\n",my_stuff.alias_file);
  310.     cprintf("   File Editor:        %s\r\n\r\n",my_stuff.editor);
  311.  
  312.     cprintf("   News Home Directory:  %s\r\n",my_stuff.home);
  313.     cprintf("   Temporary Directory:  %s\r\n",my_stuff.temp_str);
  314.     message("-- Press any key to continue --");
  315.     getch();
  316.  
  317. }
  318.  
  319. /*-------------------- show the list of active groups -----------------------*/
  320. void show_groups(ACTIVE **top, ACTIVE *this, int force)
  321. {
  322.     /*
  323.      *  This routine takes 'top', a pointer to the first line on the screen
  324.      *  and 'this' a pointer to where we want to be, and updates the screen.
  325.      *  A maker to this is maintained, and the screen is repainted, where
  326.      *  necessary
  327.      */
  328.  
  329.     static last_y;
  330.     static last_index;
  331.     int    i, ur;
  332.     ACTIVE *that;
  333.  
  334.     /*
  335.      *  If 'this' is above the 'top' or it is more than a screen length below,
  336.      *  or'this and 'top' are both zero, ie first time repaint the screen
  337.      */
  338.     if ( force || ((*top)->index > this->index) || (this->index - (*top)->index) > PAGE_LENGTH-1) {
  339.         clrscr();
  340.         textbackground(headb);    textcolor(headf);
  341.         clreol();
  342.         cprintf("         Select Newsgroup  (%s) [%ldk]\r\n", VERSION,  farcoreleft()/1000);
  343.         clreol();
  344.         cprintf("\r\n");
  345.         clreol();
  346.         textbackground(textb);    textcolor(textf);
  347.  
  348.         /* now adjust the top */
  349.         *top = this;
  350.         for (i = 0; i < PAGE_LENGTH/2; i++) {
  351.             if ((*top)->last == NULL) break;
  352.             *top = (*top)->last;
  353.         }
  354.  
  355.         that = *top;
  356.         for (i = 0; i < PAGE_LENGTH; i++) {
  357.             ur = count_unread_in_group(that);
  358.             gotoxy(7, i+4);
  359.             cprintf("%4d. %-50s", ((*top)->index)+i, that->group);
  360.             if (ur == 0)
  361.                 cprintf("     ", ur);
  362.             else
  363.                 cprintf(" %4d", ur);
  364.             cprintf(" (%d)\r\n", that->hi_num - that->lo_num);
  365.             that = that->next;
  366.             if (that == NULL) break;
  367.         }
  368.  
  369.         last_y     = this->index - (*top)->index;
  370.         last_index = this->index;
  371.     }
  372.     gotoxy(5, last_y + PAGE_HEADER);
  373.     putch(' ');
  374.  
  375.     last_y += (this->index - last_index);
  376.     gotoxy(5, last_y + PAGE_HEADER);
  377.     putch('>');
  378.     last_index = this->index;
  379.     
  380.     command("ESC=quit   TAB=next unread group   ENTER=read group   F1 or '?'=help");
  381. }
  382.  
  383.  
  384.  
  385.  
  386. /*--------------------------- process message -------------------------------*/
  387. int read_group(ACTIVE *gp)
  388. {
  389.     /*
  390.      *  We now have newsgroup.  Access the directory and try to read
  391.      *  the newsgroup articles, extracting the headers.
  392.      */
  393.  
  394.     ARTICLE *start;
  395.  
  396.     if (gp->lo_num < gp->hi_num) {
  397.         clrscr();
  398.         printf("          Select Thread  (%s)  [%ldk]\n", VERSION, farcoreleft()/1000);
  399.         printf("Group: %s ", gp->group);
  400.         start = get_headers(gp);
  401.         select_thread(gp, start);
  402.         free_header(start);
  403.     }
  404.  
  405.     return(0);
  406. }
  407.  
  408.  
  409. /*-------------------- show the list of active groups -----------------------*/
  410. void show_threads(ACTIVE *gp, ARTICLE **top, ARTICLE *this, int force)
  411. {
  412.     /*
  413.      *  This routine takes 'top', a pointer to the first line on the screen
  414.      *  and 'this' a pointer to where we want to be, and updates the screen.
  415.      *  A maker to this is maintained, and the screen is repainted, where
  416.      *  necessary
  417.      */
  418.  
  419.     static last_y;
  420.     static last_index;
  421.     int    i;
  422.     ARTICLE *that;
  423.     int    unread;
  424.  
  425.     /*
  426.      *  If 'this' is above the 'top' or it is more than a screen length below,
  427.      *  or'this and 'top' are both zero, ie first time repaint the screen
  428.      */
  429.     if ( force || ((*top)->index > this->index) || (this->index - (*top)->index) > PAGE_LENGTH-1) {
  430.         clrscr();
  431.         textbackground(headb);    textcolor(headf);
  432.         clreol();
  433.         cprintf("         Select Thread  (%s) [%ldk]\r\n", VERSION,  farcoreleft()/1000);
  434.         clreol();
  435.         cprintf("Group: %s\r\n", gp->group);
  436.         clreol();
  437.         textbackground(textb);    textcolor(textf);
  438.  
  439.         /* now adjust the top */
  440.         *top = this;
  441.         for (i = 0; i < PAGE_LENGTH/2; i++) {
  442.             if ((*top)->last == NULL) break;
  443.             *top = (*top)->last;
  444.         }
  445.  
  446.         that = *top;
  447.         for (i = 0; i < PAGE_LENGTH; i++) {
  448.             unread = count_unread_in_thread(gp, that),
  449.             gotoxy(7, i+4);
  450.             
  451.             if (unread > 0) 
  452.                 cprintf("%4d. %4d %4d %s", ((*top)->index)+i, unread, that->num_articles, that->header);
  453.             else 
  454.                 cprintf("%4d.      %4d %s", ((*top)->index)+i, that->num_articles, that->header);
  455.                 
  456.             that = that->next;
  457.             if (that == NULL) 
  458.                 break;
  459.         }
  460.  
  461.         last_y     = this->index - (*top)->index;
  462.         last_index = this->index;
  463.     }
  464.  
  465.     gotoxy(5, last_y + PAGE_HEADER);
  466.     putch(' ');
  467.  
  468.     last_y += (this->index - last_index);
  469.     gotoxy(5, last_y + PAGE_HEADER);
  470.     putch('>');
  471.     last_index = this->index;
  472.     command("ESC=select group   TAB=next unread   ENTER=next article   F1 or '?'=help");
  473. }
  474.  
  475.  
  476.  
  477.  
  478. /*-------------------------- find which group to read -----------------------*/
  479. void select_thread(ACTIVE *gp, ARTICLE *head)
  480. {
  481.     /*
  482.      *  Present the list of threads, and allow him to move up and down with
  483.      *  the arrow and PgUp and PgDn keys.  'h' for help, -/+ for searches
  484.      */
  485.  
  486.     ARTICLE *top;        /* thread at the top of the page    */
  487.     ARTICLE *this;       /* current thread                   */
  488.     ARTICLE *th;
  489.     ART_ID  *art;
  490.     int    exit_code;    /* why we are exiting the loop      */
  491.     char   sub_tmp[80];
  492.  
  493.     int    ch, i, idx, hit, a_ct;
  494.  
  495.     this = head;
  496.  
  497.     top = head;
  498.     exit_code = 0;
  499.  
  500.     show_threads(gp, &top, this, TRUE);
  501.  
  502.     while (exit_code == 0) {
  503.  
  504.         ch = getch();
  505.         switch (ch) {
  506.  
  507.             case 0      :
  508.             
  509.                 ch    = getch();
  510.                 xfile = FALSE;
  511.                 switch (ch) {
  512.  
  513.                     case Fn1    :   
  514.                         show_help(HELP_THREAD);
  515.                         show_threads(gp, &top, this, TRUE);
  516.                         break;
  517.  
  518.                     case Fn2    :
  519.                         show_values();
  520.                         show_threads(gp, &top, this, TRUE);
  521.                         break;
  522.  
  523.                     case UP_ARR :
  524.                         if (this->last != NULL) this = this->last;
  525.                         break;
  526.  
  527.                     case DN_ARR :
  528.                         if (this->next != NULL) this = this->next;
  529.                         break;
  530.  
  531.                     case PGUP   :
  532.                         for (i = 0; i < PAGE_LENGTH; i++) {
  533.                             if (this->last == NULL) break;
  534.                             this = this->last;
  535.                         }
  536.                         break;
  537.  
  538.                     case PGDN   :
  539.                         for (i = 0; i < PAGE_LENGTH; i++) {
  540.                             if (this->next == NULL) break;
  541.                             this = this->next;
  542.                         }
  543.                         break;
  544.  
  545.                     case RIGHT  :
  546.                         read_thread(gp, this, this->art_num, 1);
  547.                         show_threads(gp, &top, this, TRUE);
  548.                         break;
  549.  
  550.                     case HOME   :
  551.                         top = this = head;
  552.                         show_threads(gp, &top, this, TRUE);
  553.                         break;
  554.  
  555.                     case END    :
  556.                         this = head;
  557.                         while (this->next != NULL)
  558.                             this = this->next;
  559.                         break;
  560.                 }
  561.                 break;
  562.  
  563.             case 's'    :
  564.                 save_thread_to_disk(gp, this);
  565.                 break;
  566.  
  567.             case 'w'    :
  568.                 xfile = TRUE;
  569.                 save_thread_to_disk(gp,this);
  570.                 xfile = FALSE;
  571.                 break;
  572.  
  573.             case 'p'    :
  574.                 strcpy(sub_tmp, "");
  575.                 post(NULL, gp->group, sub_tmp);
  576.                 show_threads(gp, &top, this, TRUE);
  577.                 break;
  578.  
  579.             case '?'    :
  580.             case 'h'    :
  581.                 show_help(HELP_THREAD);
  582.                 show_threads(gp, &top, this, TRUE);
  583.                 break;
  584.  
  585.             case TAB    :
  586.                 /*
  587.                  *  Go to the next unread article.  Work through each
  588.                  *  thread, looking at each article to see if it's been
  589.                  *  read
  590.                  */
  591.  
  592.                 /* for each thread */
  593.                 th = this;
  594.                 hit = FALSE;
  595.                 while (th != NULL) {
  596.  
  597.                     art = th->art_num;
  598.                     a_ct = 0;
  599.  
  600.                     /* for each article */
  601.                     while (art != NULL) {
  602.                         idx = (int)(art->id - gp->lo_num - 1);
  603.                         a_ct++;
  604.                         if ( *((gp->read_list)+idx) == FALSE) {
  605.                             hit = TRUE;
  606.                             break;
  607.                         }
  608.                         art = art->next_art;
  609.                     }
  610.                     if (hit) break;
  611.                     th = th->next;
  612.                 }
  613.  
  614.                 if (hit) {
  615.                     this = th;
  616.                     read_thread(gp, this, art, a_ct);
  617.                     show_threads(gp, &top, this, TRUE);
  618.                 } else {
  619.                     message("-- No more articles to read --");
  620.                 }
  621.                 break;
  622.  
  623.             case 'c'    :
  624.                 mark_group_as_read(gp);
  625.                 show_threads(gp, &top, this, TRUE);
  626.                 break;
  627.  
  628.             case ENTER  :
  629.                 read_thread(gp, this, this->art_num, 1);
  630.                 show_threads(gp, &top, this, TRUE);
  631.                 break;
  632.  
  633.             case ESCAPE :
  634.                 exit_code = EX_QUIT;
  635.                 break;
  636.         };
  637.         if (exit_code == 0)
  638.             show_threads(gp, &top, this, FALSE);
  639.     }
  640. }
  641.  
  642.  
  643.  
  644.  
  645. /*------------------------ save a thread ------------------------------*/
  646. void save_thread_to_disk(ACTIVE *gp, ARTICLE *this)
  647. {
  648.     ART_ID *id;
  649.     char   *fn;
  650.     TEXT   *tx;
  651.     LINE   *ln;
  652.     int    a_ct;
  653.     char   fnx[80];
  654.     int    ch;
  655.     FILE   *tmp;
  656.  
  657.     if (xfile == TRUE)                /* extract function */
  658.        strcpy(fnx,"\\extract.nws");
  659.     else {                             /* save function    */
  660.        message("Enter filename? ");
  661.        gets(fnx);
  662.     }
  663.  
  664.     tmp = NULL;
  665.  
  666.     if (xfile == TRUE) {        /* extract function */
  667.        if (access(fnx, 0) == 0) {
  668.           if ((tmp = fopen(fnx, "at")) == NULL) {
  669.              message("*** Cannot open file for appending - "
  670.              "please any key to continue ***");
  671.              getch();
  672.           }
  673.        } 
  674.        else {
  675.           if ((tmp = fopen(fnx, "wt")) == NULL) {
  676.              message("*** Cannot open file for output - press a key to continue ***");
  677.              getch();
  678.           }
  679.        }
  680.     } 
  681.     else {                /* save function */
  682.         if (access(fnx, 0) == 0) {
  683.             message("File exists - append(y/n)? ");
  684.             while (((ch = getch()) != 'y') && (ch != 'n'));
  685.             if (ch == 'y') {
  686.                 if ((tmp = fopen(fnx, "at")) == NULL) {
  687.                     message("*** Cannot open file for appending - "
  688.                     "please any key to continue ***");
  689.                     getch();
  690.                 }
  691.             }
  692.         } 
  693.         else {
  694.             if ((tmp = fopen(fnx, "wt")) == NULL) {
  695.                 message("*** Cannot open file for output - press a key to continue ***");
  696.                 getch();
  697.             }     
  698.         }    
  699.     }
  700.  
  701.     if (tmp != NULL) {
  702.  
  703.         fn = make_news_group_name(gp->group);
  704.  
  705.         id = this->art_num;
  706.         a_ct = 0;
  707.  
  708.         while (id != NULL) {
  709.             if (xfile == TRUE) {
  710.                fputs("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\n",tmp);
  711.             }
  712.  
  713.             tx = load_article(fn, id->art_off);
  714.  
  715.             ln = tx->top;
  716.             while (ln != NULL) {
  717.                 fputs(ln->data, tmp);
  718.                 ln = ln->next;
  719.             }
  720.             fputs("\n", tmp);
  721.             a_ct++;
  722.             id = id->next_art;
  723.             free_article(tx);
  724.         }
  725.         fclose(tmp);
  726.     }
  727.  
  728.     message("");
  729. }
  730.  
  731.  
  732.  
  733. /*------------------------ read a thread ------------------------------*/
  734. int read_thread(ACTIVE *gp, ARTICLE *this, ART_ID *first, int a_ct)
  735. {
  736.     ART_ID *id;
  737.     char   *fn;
  738.     int    idx, res;
  739.     TEXT   *tx;
  740.     char   author[128], msg_id[128];
  741.     CROSS_POSTS *h, *h0;
  742.     ACTIVE *gx;
  743.  
  744.  
  745.     fn = make_news_group_name(gp->group);
  746.  
  747.     id = first;
  748.  
  749.     while (id != NULL) {
  750.  
  751.         tx = load_article(fn, id->art_off);
  752.  
  753.         res = read_article(gp, tx, this->header, a_ct, this->num_articles);
  754.  
  755.         /* mark this article as read */
  756.         idx = (int) ((id->id) - gp->lo_num - 1);
  757.         *((gp->read_list)+idx) = TRUE;
  758.  
  759.         /* mark the crossposts */
  760.         get_his_stuff(tx, author, msg_id);
  761.         if ((h0 = look_up_history(msg_id, gp->group)) != NULL) {
  762.  
  763.             h = h0;
  764.             while (h != NULL) {
  765.                 gx = find_news_group(h->group);
  766.                 if (gx) {
  767.                     idx = (int) ((h->art_num) - gx->lo_num - 1);
  768.                     *((gx->read_list)+idx) = TRUE;
  769.                 }
  770.                 h = h->next;
  771.             }
  772.  
  773.             free_cross_post_list(h0);
  774.         }
  775.  
  776.         if (res == EX_QUIT) {
  777.             free_article(tx);
  778.             break;
  779.         }
  780.  
  781.         if (res == EX_NEXT_UNREAD) {
  782.  
  783.             while (id != NULL) {
  784.                 idx = (int)(id->id - gp->lo_num - 1);
  785.                 if ( *((gp->read_list)+idx) == FALSE) {
  786.                     break;
  787.                 }
  788.                 a_ct++;
  789.                 id = id->next_art;
  790.             }
  791.  
  792.             if (id == NULL)
  793.                 message("-- No more articles in thread --");
  794.  
  795.         } else {
  796.             if (res == LEFT && id->prev_art) { /* only go back if we can */
  797.                 a_ct--;
  798.                 id = id->prev_art;
  799.             }
  800.             else {
  801.                 if (res != LEFT) {
  802.                     a_ct++;
  803.                     id = id->next_art;
  804.                 }
  805.             }
  806.         }
  807.  
  808.         free_article(tx);
  809.  
  810.     }
  811.  
  812.     return(res);
  813. }
  814.  
  815.  
  816.  
  817.  
  818. /*------------------------- read the headers --------------------------*/
  819. ARTICLE *get_headers(ACTIVE *gp)
  820. {
  821.     /*
  822.      *  Read the files and get the headers
  823.      */
  824.  
  825.     char *fn;
  826.     char buf[256], fnx[256], *buf_p;
  827.     long g, n_read;
  828.     FILE *tmp_file;
  829.  
  830.     ARTICLE *start, *that, *tmp;
  831.     ART_ID  *art_this, *new;
  832.     int     ct_art;
  833.  
  834.     n_read = 0;
  835.     ct_art = 0;
  836.     start = NULL;
  837.  
  838.     fn = make_news_group_name(gp->group);
  839.     sprintf(fnx, "%s.IDX", fn);
  840.     gotoxy(1,25);
  841.     printf("articles processed: ");
  842.  
  843.     if ((tmp_file = fopen(fnx, "rb")) != NULL) {
  844.  
  845.         for (g = gp->lo_num+1; g <= gp->hi_num; g++) {
  846.  
  847.             if ((n_read++ % 10) == 0) {
  848.                 gotoxy(21,25);
  849.                 printf("%d", n_read-1);
  850.             }
  851.  
  852.             /*
  853.              *  Read the index
  854.              *  Search the linked list for the subject
  855.              *    - allocate a new subject if necessary
  856.              *    - add to the existing list
  857.              */
  858.              
  859.             if (fgets(buf, 255, tmp_file) == NULL) {
  860.                 gotoxy(1,25);
  861.                 fprintf(stderr, "\nsnews: index file is corrupt\n");
  862.                 exit(1);
  863.             }
  864.  
  865.             /* check all is in sync */
  866.             if (g != atol(buf+9)) {
  867.                 gotoxy(1,25);
  868.                 fprintf(stderr, "\nsnews: article %ld found when %ld expected\n", atol(buf+9), g);
  869.                 exit(1);
  870.             }
  871.  
  872.             /* skip the two eight digit numbers and the 9 and the spaces */
  873.             buf_p = buf+28;
  874.  
  875.             eat_gunk(buf_p);
  876.             tmp = start;
  877.             while (tmp != NULL) {
  878.                 if (stricmp(buf_p, tmp->header) == 0)
  879.                     break;
  880.                 tmp = tmp->next;
  881.             }
  882.  
  883.             if (tmp != NULL) {
  884.  
  885.                 /* allocate new article number */
  886.                 new = xmalloc(sizeof (ART_ID));
  887.                 new->id = g;
  888.                 new->art_off = atol(buf);
  889.                 new->next_art = NULL;
  890.                 new->prev_art = NULL;
  891.                 tmp->num_articles++;
  892.  
  893.                 /* place it at the end */
  894.                 art_this = tmp->art_num;
  895.                 while (art_this->next_art != NULL) {
  896.                     art_this = art_this->next_art;
  897.                 }
  898.  
  899.                 art_this->next_art = new;
  900.                 new->prev_art      = art_this;
  901.             }
  902.             else {
  903.  
  904.                 /* not found - allocate new thread */
  905.                 if (start == NULL) {
  906.                     start = that = xmalloc(sizeof (ARTICLE));
  907.                     start->last = NULL;
  908.                     start->next = NULL;
  909.                     start->index = ct_art;
  910.                 } else {
  911.                     ct_art++;
  912.                     that->next = xmalloc(sizeof (ARTICLE));
  913.                     that->next->last = that;
  914.                     that = that->next;
  915.                     that->next = NULL;
  916.                     that->index = ct_art;
  917.                 }
  918.  
  919.                 /* store article data */
  920.                 strcpy(that->header, buf_p);
  921.                 that->num_articles = 1;
  922.                 that->art_num = xmalloc(sizeof (ART_ID));
  923.                 that->art_num->next_art = NULL;
  924.                 that->art_num->prev_art = NULL;
  925.                 that->art_num->id = g;
  926.                 that->art_num->art_off = atol(buf);
  927.  
  928.             }
  929.  
  930.             that->next = NULL;
  931.  
  932.         }
  933.  
  934.         fclose(tmp_file);
  935.  
  936.     } 
  937.     else {
  938.         gotoxy(1,25);
  939.         fprintf(stderr, "\nsnews: can't open index file %s\n", fnx);
  940.         exit(1);
  941.     }
  942.  
  943.     return(start);
  944. }
  945.  
  946.  
  947.  
  948. /*------------------------ clean up subject line ----------------------------*/
  949. void eat_gunk(char *buf)
  950. {
  951.     /*
  952.      *  This routine take the header line, and strips the
  953.      *  word header word, 'Re:', and any extra blanks, trim to 59 chars
  954.      */
  955.  
  956.     char *p = buf;
  957.  
  958.     buf[58] = 0;
  959.     
  960.     while (*p && isspace(*p)) p++;
  961.     
  962.     while (*p && !strnicmp(p, "re:", 3)) {
  963.         p += 3;
  964.         while (*p && isspace(*p)) p++;
  965.     }
  966.     
  967.     if (!p) 
  968.         strcpy(buf, "<no subject>");
  969.     else 
  970.         strcpy(buf, p);
  971.     
  972.     p = buf + strlen(buf) - 1;
  973.     while (*p && (*p == '\n' || *p == '\t' || *p == '\r')) 
  974.         p--;
  975.     p++; *p = '\0';
  976. }
  977.  
  978.  
  979. /*-------------------- release the subject structures ---------------------*/
  980. void free_header(ARTICLE *start)
  981. {
  982.     /*
  983.      *  Work our way through the subject structure releasing all the
  984.      *  memory
  985.      */
  986.  
  987.     ARTICLE *a, *t;
  988.     ART_ID  *u, *v;
  989.  
  990.     a = start;
  991.  
  992.     while (a != NULL) {
  993.  
  994.         u = a->art_num;
  995.         while (u != NULL) {
  996.             v = u->next_art;
  997.             free(u);
  998.             u = v;
  999.         }
  1000.  
  1001.         t = a->next;
  1002.         free(a);
  1003.         a = t;
  1004.     }
  1005. }
  1006.  
  1007.  
  1008. /*------------------- count unread articles in a thread --------------------*/
  1009. int count_unread_in_thread(ACTIVE *gp, ARTICLE *a)
  1010. {
  1011.     /*
  1012.      *  Take the thread 'a' for the given group 'gp' and return the number
  1013.      *  of unread articles
  1014.      */
  1015.  
  1016.     ART_ID *id;
  1017.     int    ct, idx;
  1018.  
  1019.     ct = 0;
  1020.     id = a->art_num;
  1021.  
  1022.     while (id != NULL) {
  1023.         idx = (int)(id->id - gp->lo_num - 1);
  1024.         if (*((gp->read_list)+idx) == FALSE)
  1025.             ct++;
  1026.         id = id->next_art;
  1027.     }
  1028.  
  1029.     return(ct);
  1030. }
  1031.  
  1032.  
  1033.  
  1034. /*------------------- count unread articles in a group --------------------*/
  1035. int count_unread_in_group(ACTIVE *gp)
  1036. {
  1037.     /*
  1038.      *  Take the thread 'a' for the given group 'gp' and return the number
  1039.      *  of unread articles
  1040.      */
  1041.  
  1042.     int articles, ct, i;
  1043.  
  1044.     ct = 0;
  1045.     articles = (int)(gp->hi_num - gp->lo_num);
  1046.  
  1047.     for (i = 0; i < articles; i++) {
  1048.         if (*((gp->read_list)+i) == FALSE)
  1049.             ct++;
  1050.     }
  1051.  
  1052.     return(ct);
  1053. }
  1054.  
  1055.  
  1056.  
  1057. /*------------------- count unread articles in a group --------------------*/
  1058. void mark_group_as_read(ACTIVE *gp)
  1059. {
  1060.     /*
  1061.      *  Take the thread 'a' for the given group 'gp' and return the number
  1062.      *  of unread articles
  1063.      */
  1064.  
  1065.     int articles, i, ch;
  1066.  
  1067.     message("Mark all articles as read (y/n)? ");
  1068.     while (((ch = getch()) != 'y') && (ch != 'n') && (ch != ESCAPE));
  1069.  
  1070.     if (ch == 'y') {
  1071.  
  1072.         articles = (int)(gp->hi_num - gp->lo_num);
  1073.  
  1074.         for (i = 0; i < articles; i++)
  1075.             *((gp->read_list)+i) = TRUE;
  1076.  
  1077.     }
  1078.  
  1079. }
  1080.  
  1081.  
  1082.  
  1083. /*-------------------------- status message ---------------------------------*/
  1084. void message(char *msg)
  1085. {
  1086.     int x;
  1087.  
  1088.     x = 40 - (strlen(msg)/2);
  1089.  
  1090.     gotoxy(1,24);
  1091.     textbackground(msgb);  textcolor(msgf);
  1092.     clreol();
  1093.     gotoxy(x,24);
  1094.     cprintf("%s", msg);
  1095.     textbackground(textb);    textcolor(textf);
  1096. }
  1097.  
  1098.  
  1099. /*-------------------------- status message ---------------------------------*/
  1100. void lmessage(char *msg)
  1101. {
  1102.     gotoxy(1,24);
  1103.     textbackground(msgb);  textcolor(msgf);
  1104.     clreol();
  1105.     cprintf("%s", msg);
  1106.     textbackground(textb);    textcolor(textf);
  1107. }
  1108.  
  1109.  
  1110. /*-------------------------- status message ---------------------------------*/
  1111. void command(char *msg)
  1112. {
  1113.     int x;
  1114.  
  1115.     x = 40 - (strlen(msg)/2);
  1116.     gotoxy(1,25);
  1117.     textbackground(msgb);  textcolor(msgf);
  1118.     clreol();
  1119.  
  1120.     gotoxy(x,25);
  1121.     cprintf(msg);
  1122.  
  1123.     textbackground(textb);    textcolor(textf);
  1124. }
  1125.  
  1126. /* end */
  1127.